package de.mlp_distributed.mlp.math.jblas;

import java.util.Iterator;

import org.jblas.DoubleMatrix;

import com.google.common.collect.AbstractIterator;

import de.mlp_distributed.mlp.math.function.DoubleDoubleFunction;
import de.mlp_distributed.mlp.math.function.DoubleFunction;
import de.mlp_distributed.mlp.math.function.PlusMult;
import de.mlp_distributed.mlp.math.mahout.CardinalityException;
import de.mlp_distributed.mlp.math.mahout.Matrix;
import de.mlp_distributed.mlp.math.mahout.Vector;

public class JDenseVector implements Vector {

	private int size;
	private double lengthSquared = 0.0;
	protected DoubleMatrix jdm;

	public JDenseVector() {
		super();
	}

	public JDenseVector(final int size) {
		this.size = size;
		this.jdm = new DoubleMatrix(new double[size]);
	}

	public JDenseVector(final double[] values) {
		this.size = values.length;
		this.jdm = new DoubleMatrix(values);
	}

	public JDenseVector(final DoubleMatrix result) {
		this.jdm = result;
		this.size = this.jdm.getRows();
	}

	@Override
	public JDenseVector clone() {
		final JDenseVector clone = new JDenseVector(this.size);
		for (int col = 0; col < this.size; col++) {
			clone.set(col, this.get(col));
		}
		return clone;
	}

	@Override
	public String asFormatString() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public Vector assign(final double value) {
		for (int i = 0; i < this.size; i++) {
			this.setQuick(i, value);
		}
		return this;
	}

	@Override
	public Vector assign(final double[] values) {
		if (this.size != values.length) {
			throw new CardinalityException(this.size, values.length);
		}
		for (int i = 0; i < this.size; i++) {
			this.setQuick(i, values[i]);
		}
		return this;
	}

	@Override
	public Vector assign(final Vector other) {
		if (this.size != other.size()) {
			throw new CardinalityException(this.size, other.size());
		}
		for (int i = 0; i < this.size; i++) {
			this.setQuick(i, other.getQuick(i));
		}
		return this;
	}

	@Override
	public Vector assign(final DoubleFunction function) {
		final Iterator<Element> it = function.apply(0) == 0 ? this.iterateNonZero() : this.iterator();
		while (it.hasNext()) {
			final Element e = it.next();
			e.set(function.apply(e.get()));
		}
		return this;
	}

	@Override
	public Vector assign(final Vector other, final DoubleDoubleFunction function) {
		if (this.size() != other.size()) {
			throw new CardinalityException(this.size(), other.size());
		}
		// is there some other way to know if function.apply(0, x) = x for all
		// x?
		if (function instanceof PlusMult) {
			final Iterator<Element> it = other.iterateNonZero();
			Element e;
			while (it.hasNext() && ((e = it.next()) != null)) {
				this.setQuick(e.index(), function.apply(this.getQuick(e.index()), e.get()));
			}
		} else {
			for (int i = 0; i < this.size(); i++) {
				this.setQuick(i, function.apply(this.getQuick(i), other.getQuick(i)));
			}
		}
		this.lengthSquared = -1;
		return this;
	}

	@Override
	public Vector assign(final DoubleDoubleFunction f, final double y) {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public int size() {
		return this.size;
	}

	@Override
	public boolean isDense() {
		return true;
	}

	@Override
	public boolean isSequentialAccess() {
		return true;
	}

	@Override
	public Iterator<Element> iterator() {
		return new AllIterator();
	}

	@Override
	public Iterator<Element> iterateNonZero() {
		return new NonDefaultIterator();
	}

	@Override
	public Element getElement(final int index) {
		return new LocalElement(index);
	}

	@Override
	public Vector divide(final double x) {
		this.jdm.div(x);
		return this;
	}

	@Override
	public double dot(final Vector x) {
		return this.jdm.dot(((JDenseVector) x).jdm);
	}

	@Override
	public double get(final int index) {
		return this.jdm.get(index);
	}

	@Override
	public double getQuick(final int index) {
		return this.get(index);
	}

	@Override
	public Vector like() {
		return new JDenseVector(this.size);
	}

	@Override
	public Vector minus(final Vector x) {
		this.jdm.sub(((JDenseVector) x).jdm);
		return this;
	}

	@Override
	public Vector normalize() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public Vector normalize(final double power) {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public Vector logNormalize() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public Vector logNormalize(final double power) {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public double norm(final double power) {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public double minValue() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public int minValueIndex() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public double maxValue() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public int maxValueIndex() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public Vector plus(final double x) {
		this.jdm.add(x);
		return this;
	}

	@Override
	public Vector plus(final Vector x) {
		this.jdm.add(((JDenseVector) x).jdm);
		return this;
	}

	@Override
	public void set(final int index, final double value) {
		this.jdm.put(index, 0, value);
	}

	@Override
	public void setQuick(final int index, final double value) {
		this.set(index, value);
	}

	@Override
	public int getNumNondefaultElements() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public Vector times(final double x) {
		if (x == 0.0) {
			return this.like();
		}

		final Vector result = this.like().assign(this);
		if (x == 1.0) {
			return result;
		}

		final Iterator<Element> iter = result.iterateNonZero();
		while (iter.hasNext()) {
			final Element element = iter.next();
			element.set(element.get() * x);
		}

		return result;
	}

	@Override
	public Vector times(final Vector x) {
		this.jdm.mul(((JDenseVector) x).jdm);
		return this;
	}

	@Override
	public Vector viewPart(final int offset, final int length) {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public double zSum() {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public Matrix cross(final Vector other) {
		final Matrix result = this.matrixLike(this.size, other.size());
		for (int row = 0; row < this.size; row++) {
			result.assignRow(row, other.times(this.getQuick(row)));
		}
		return result;
	}

	protected Matrix matrixLike(final int rows, final int columns) {
		return new JDenseMatrix(rows, columns);
	}

	@Override
	public double aggregate(final DoubleDoubleFunction aggregator, final DoubleFunction map) {
		if (this.size < 1) {
			throw new IllegalArgumentException("Cannot aggregate empty vector");
		}
		double result = map.apply(this.getQuick(0));
		for (int i = 1; i < this.size; i++) {
			result = aggregator.apply(result, map.apply(this.getQuick(i)));
		}
		return result;
	}

	@Override
	public double aggregate(final Vector other, final DoubleDoubleFunction aggregator, final DoubleDoubleFunction combiner) {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	@Override
	public double getLengthSquared() {
		double result = 0.0;
		for (int i = 0; i < this.size; i++) {
			final double value = this.get(i);
			result += value * value;
		}
		this.lengthSquared = result;
		return result;
	}

	@Override
	public double getDistanceSquared(final Vector v) {
		// TODO Auto-generated method stub
		throw new IllegalStateException("Unbound label");
	}

	private final class DenseElement implements Element {

		int index;

		@Override
		public double get() {
			return JDenseVector.this.get(this.index);
		}

		@Override
		public int index() {
			return this.index;
		}

		@Override
		public void set(final double value) {
			JDenseVector.this.lengthSquared = -1;
			JDenseVector.this.set(this.index, value);
		}
	}

	private final class NonDefaultIterator extends AbstractIterator<Element> {

		private final DenseElement element = new DenseElement();
		private int index = 0;

		@Override
		protected Element computeNext() {
			while ((this.index < JDenseVector.this.size()) && (JDenseVector.this.get(this.index) == 0.0)) {
				this.index++;
			}
			if (this.index < JDenseVector.this.size()) {
				this.element.index = this.index;
				this.index++;
				return this.element;
			} else {
				return this.endOfData();
			}
		}

	}

	private final class AllIterator extends AbstractIterator<Element> {

		private final DenseElement element = new DenseElement();

		private AllIterator() {
			this.element.index = -1;
		}

		@Override
		protected Element computeNext() {
			if ((this.element.index + 1) < JDenseVector.this.size()) {
				this.element.index++;
				return this.element;
			} else {
				return this.endOfData();
			}
		}

	}

	protected final class LocalElement implements Element {
		int index;

		LocalElement(final int index) {
			this.index = index;
		}

		@Override
		public double get() {
			return JDenseVector.this.getQuick(this.index);
		}

		@Override
		public int index() {
			return this.index;
		}

		@Override
		public void set(final double value) {
			JDenseVector.this.setQuick(this.index, value);
		}
	}

}
